package server

import (
	"database/sql/driver"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/dailing/levlog"
	"github.com/go-xorm/xorm"
	"io/ioutil"
	"net/http"
	"os"
	"path"
	"reflect"
	"strconv"
	"time"
)

type NullTime struct {
	Time  time.Time
	Valid bool // Valid is true if Time is not NULL
}

// Scan implements the Scanner interface.
func (nt *NullTime) Scan(value interface{}) error {
	nt.Time, nt.Valid = value.(time.Time)
	return nil
}

// Value implements the driver Valuer interface.
func (nt NullTime) Value() (driver.Value, error) {
	if !nt.Valid {
		return nil, nil
	}
	return nt.Time, nil
}

type JsonType map[string](interface{})

func NewJson() *JsonType {
	retval := make(JsonType)
	return &retval
}
func NewJsonFromString(s string) *JsonType {
	retval := make(JsonType)
	err := json.Unmarshal([]byte(s), &retval)
	levlog.E(err)
	return &retval
}

func (j *JsonType) GetString(key string) string {
	if f, ok := (*j)[key].(string); ok {
		return f
	}
	if f, ok := (*j)[key].([]byte); ok {
		return string(f)
	}
	levlog.Error("Error convert ", key)
	return ""
}

func (j *JsonType) GetBytes(key string) []byte {
	if f, ok := (*j)[key].([]byte); ok {
		return f
	}
	if f, ok := (*j)[key].(string); ok {
		return []byte(f)
	}
	levlog.Error("Error convert :", key)
	return nil
}

func (j *JsonType) GetJson(key string) *JsonType {
	if f, ok := (*j)[key].(*JsonType); ok {
		return f
	}
	levlog.Warning("Not JsonType,", (*j)[key])
	payload, err := json.Marshal((*j)[key])
	levlog.E(err)
	return NewJsonFromString(string(payload))
}

func (j *JsonType) GetInt(key string) int {
	val, ok := (*j)[key]
	if !ok {
		return 0
	}
	if s, ok := val.(string); ok {
		i, err := strconv.Atoi(s)
		levlog.E(err)
		return i
	}
	if i, ok := val.(int); ok {
		return i
	}
	if i, ok := val.(int64); ok {
		return int(i)
	}
	if i, ok := val.(float64); ok {
		return int(i)
	}
	if i, ok := val.(float32); ok {
		return int(i)
	}
	levlog.Error("Get Key Error ", reflect.TypeOf(val))
	return 0
}

func (j *JsonType) Set(key string, val interface{}) {
	levlog.Trace("key", key)
	(*j)[key] = val
}

func (j *JsonType) UnmarshalBody(s interface{}) error {
	return json.Unmarshal(j.GetBytes("req_body"), s)
}

func JsonReq(f func(w http.ResponseWriter, r *http.Request) *JsonType) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		retval := f(w, r)
		retv, err := json.Marshal(retval)
		levlog.E(err)
		w.Write(retv)
	}
}

////////////////////////////////////////////////////////////

type JsonRespReqFunc func(*JsonType) (*JsonType, int)

const (
	RESPONSE_TYPE_JSON = iota
	RESPONSE_TYPE_ERROR
	RESPONSE_TYPE_RAW
	RESPONSE_TYPE_MIDWARE
)

func HandleReq(funcList ...JsonRespReqFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		j := NewJson()
		body, err := ioutil.ReadAll(r.Body)
		levlog.Trace("---------------------URL----------------------")
		levlog.Trace(r.URL.Path)
		levlog.Trace("---------------------Body---------------------")
		levlog.Trace(string(body))
		levlog.Trace(r.Host)
		levlog.E(err)
		j.Set("req_token", r.Header.Get("token"))
		j.Set("req_body", string(body))
		j.Set("req_url", r.URL.Path)
		var lastRet *JsonType
		var succ int
		for _, f := range funcList {
			lastRet, succ = f(j)
			if succ == RESPONSE_TYPE_ERROR {
				w.WriteHeader(lastRet.GetInt("code"))
				w.Write(lastRet.GetBytes("info"))
				return
			}
		}
		var payload []byte
		if succ == RESPONSE_TYPE_RAW {
			payload = lastRet.GetBytes("payload")
		} else if succ == RESPONSE_TYPE_JSON {
			payload, err = json.Marshal(lastRet)
			levlog.E(err)
			if err != nil {
				w.WriteHeader(500)
				w.Write([]byte(err.Error()))
			}
		}
		w.WriteHeader(200)
		w.Write(payload)
	}
}

func BodyToJson(jsonType *JsonType) (*JsonType, int) {
	j := NewJson()
	err := json.Unmarshal(jsonType.GetBytes("req_body"), j)
	if err != nil {
		levlog.E(err)
		return ResponseError(403, "Request Data Error")
	}
	jsonType.Set("req_json", j)
	return NoResponse()
}

func ResponseError(code int, info string) (*JsonType, int) {
	j := NewJson()
	j.Set("code", code)
	j.Set("info", info)
	return j, RESPONSE_TYPE_ERROR
}

func ResponseJson(j *JsonType) (*JsonType, int) {
	return j, RESPONSE_TYPE_JSON
}

func ResponseAnyToJson(any interface{}) (*JsonType, int) {
	payload, err := json.Marshal(any)
	levlog.E(err)
	return ResponseRaw(string(payload))
}

func ResponseRaw(s string) (*JsonType, int) {
	j := NewJson()
	j.Set("payload", s)
	return j, RESPONSE_TYPE_RAW
}
func ResponseSucc() (*JsonType, int) {
	return ResponseRaw("succ")
}
func NoResponse() (*JsonType, int) {
	return nil, RESPONSE_TYPE_MIDWARE
}

/////////////////////////// app related ///////////////
func (app *App) Init() *App {
	var err error
	app.engine, err = xorm.NewEngine("mysql", "root:123456@tcp(mysql)/"+DB_NAME)
	levlog.F(err)
	err = app.engine.Charset("utf8").Sync2(
		new(Image),
		new(ImageAnnotation),
		new(SessionAnnotation),
		new(Patient),
		new(Person),
		new(AnnoSession),
		new(ImageInSession),
	)
	levlog.F(err)

	//patient := Patient{}
	//json.Unmarshal([]byte(`{"Name": "fuck"}`), &patient)
	//levlog.Info(patient)

	//app.db_storage, err = sqlx.Connect("mysql", "root:123456@/"+DB_NAME)
	levlog.F(err)

	app.AddUser("t", "t")
	app.AddUser("duxinhua", "duxinhua")
	app.AddUser("wangxiangning", "wangxiangning")
	app.AddUser("wangyan", "wangyan")
	app.AddUser("lishuting", "lishuting")
	app.AddUser("JuicyDwarf", "02fatherNAMEmajor21")
	app.AddUser("GreedyConquerer", "86becomeTRUCKdance76")
	app.AddUser("UltraHawk", "35portugalFULLfifth22")
	app.AddUser("ChiefWooden", "56flierKILLsweet99")
	app.AddUser("KingDefiant", "78stayREALoffer51")
	app.AddUser("FranticGod", "47bestCENTURYplace81")
	app.AddUser("PrinceRapid", "47childrenTHOUGHraise85")
	app.AddUser("GoldPaladin", "41decimalDATEpleasant54")
	app.AddUser("UncleSalty", "14caseEXPRESSbelgium57")
	app.AddUser("PoisonBow", "77shownFINGERconsider10")
	app.AddUser("OrganizedWarrior", "81fearMETALbehind34")
	app.AddUser("AdmiralIce", "66nailWAVESbutter72")
	app.AddUser("VileArrow", "61cornINCLUDEonly94")
	app.AddUser("SilverDude", "99outerMARCHlength49")
	app.AddUser("PrinceColossal", "38destroyCOMPANYmove00")
	app.AddUser("CorporalIron", "16comparePLAYtable53")
	app.AddUser("LordTitanium", "73clothCRIEDpicked04")
	app.AddUser("GrilledPeanut", "87grewARRIVEfirst56")

	//levlog.Info(app.GetImageByName("20170504"))
	return app
}

func (app *App) getNextImgList(userID, permission, start, limit int) ([]AnnoSession, error) {
	var session []AnnoSession
	err := app.engine.SQL(`
		SELECT
		  anno_session.*,
		  min_anno_per_img
		FROM anno_session
		  JOIN
		  (
			# get session id and min imgannotations in that session
			SELECT
			  session,
			  min(anno_count.annos_per_img) AS min_anno_per_img
			FROM image_in_session
			  LEFT JOIN image AS imgtable
				ON imgtable.id = image
			  LEFT JOIN (
						  # Get the number of annotations per image
						  SELECT
							image    AS imageID,
							count(*) AS annos_per_img
						  FROM image_annotation
						  GROUP BY image
						) AS anno_count
				ON anno_count.imageID = image
			GROUP BY session
		  ) AS sessionid_with_min_img
			ON id = sessionid_with_min_img.session
		WHERE
		  #   check there are images in session
		  id IN
		  (
			SELECT anno_session.id
			FROM anno_session
			WHERE id IN
				  (SELECT DISTINCT session
				   FROM image_in_session)
		  )
		  AND
		  # Check that session already annotated by me is not in list
		  id NOT IN
		  (
			SELECT DISTINCT session
			FROM image_in_session
			  JOIN
			  (SELECT DISTINCT image
			   FROM image_annotation
			   WHERE person = ?
			  ) AS annotated_imgs_by_me
				ON annotated_imgs_by_me.image = image_in_session.image
		  )
		  AND
		  # Check that all image has visit_permission for each image in session
		  id NOT IN (
			SELECT DISTINCT image_in_session.session as id
			FROM image_in_session
			  JOIN image
				ON image_in_session.image = image.id
			WHERE image.visit_permision < ? OR image.grade=0
		  )
		  AND
		  (min_anno_per_img < 3 OR min_anno_per_img IS NULL)
		ORDER BY sessionid_with_min_img.min_anno_per_img, anno_session.priority, rand()
		LIMIT ?
		OFFSET ?
		;`,
		userID, permission, limit, start).
		Find(&session)
	//err := app.db_storage.Select(&img, querrystr)
	levlog.E(err)
	return session, err
}

func (app *App) AddUser(name, psw string) error {
	if len(name) < 1 || len(psw) < 1 {
		return errors.New("Wrong User Name")
	}
	salt := randStringGen(20)
	p := PswHash(psw, salt)
	person := Person{
		Name:       name,
		Salt:       salt,
		Psw:        p,
		Permission: 0,
	}
	_, err := app.engine.Insert(person)
	levlog.E(err)
	return err
}

func (app *App) AddImage(img *Image) error {
	// check file exists
	absPath := path.Join("/data", img.Path)
	if _, err := os.Stat(absPath); err != nil {
		return err
	}
	affected, err := app.engine.Insert(img)
	if err == nil && affected != 1 {
		return errors.New("Affected not 1!")
	}
	return err
}

func (app *App) AddSession(session AnnoSession, imgList []int64) (id int64, err error) {
	id, err = app.engine.InsertOne(session)
	levlog.E(err)
	if err != nil {
		return
	}
	if imgList == nil {
		for _, i := range imgList {
			// add a image id into set
			app.engine.InsertOne(ImageInSession{
				Session: id,
				Image:   i,
			})
		}
	}
	return
}

func (app *App) GetImageByName(name string) []Image {
	var images []Image
	namestr := fmt.Sprintf("'%%%v%%'", name)
	levlog.Info(namestr)
	err := app.engine.SQL("select * from image where path like " + namestr).
		Find(&images)
	levlog.E(err)
	return images
}
